#include "Application.h"
#include <iostream>
#include <GL/glut.h>
#include "Utilities.h"
#include <GL/glui.h>

using namespace std;

Simulation Application::simulation;			// the smoke simulation
Visualization Application::visualization;	// visualization of the simulation


//size of the graphics window, in pixels
int Application::winWidth;
int Application::winHeight;		

GLUI* Application::glui;						// user interface
int Application::main_window;

GLUI_RadioGroup *color_radiobuttons;
GLUI_RadioGroup *color_radiobuttons_vec;
GLUI_Listbox *colormap_menu;
GLUI_Listbox *colormap_menu_vec;
GLUI_RadioGroup *scalar_dataset_radiobuttons;
GLUI_RadioGroup *vector_dataset_radiobuttons;
GLUI_RadioGroup *scale_radiobuttons;
GLUI_RadioGroup *scale_radiobuttons_vec;

GLUI_Spinner *hue_spinner;
GLUI_Spinner *sat_spinner;
GLUI_Spinner *val_spinner;


GLUI_Spinner *hue_spinner_vec;
GLUI_Spinner *sat_spinner_vec;
GLUI_Spinner *val_spinner_vec;


GLUI_Spinner *levels_spinner;
GLUI_Spinner *levels_spinner_vec;
GLUI_Spinner *glyph_scale_spinner;
GLUI_Button *pause_button;

void Application::update()
{
	glutSetWindow(main_window);
	simulation.do_one_simulation_step();
	glutPostRedisplay();
}

void Application::initialize(int *argc, char** argv)
{	
	glutInit(argc, argv);
	glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE | GLUT_DEPTH);
	glutInitWindowSize(1000, 800);
	main_window = glutCreateWindow("Real-time smoke simulation and visualization");
        
        // blie bloe bla
		
	// pass static functions as callback to GLUT
	glutDisplayFunc(display);
	glutMotionFunc(drag);
	glutKeyboardFunc(keyboard);

	// pass static functions as callback to GLUI
	GLUI_Master.set_glutReshapeFunc(reshape);  
	GLUI_Master.set_glutIdleFunc(update);

	simulation.init_simulation(Simulation::DIM);	//initialize the simulation data structures
	initUI();   
	
    setWidgets();	
	glutMainLoop();									// enter main loop
    
}

// output usage instructions
void Application::outputUsage()
{
	cout << "Fluid Flow Simulation and Visualization\n";
	cout << "=======================================\n";
	cout << "Click and drag the mouse to steer the flow!\n";
	cout << "T/t:   increase/decrease simulation timestep\n";
	cout << "S/s:   increase/decrease hedgehog scaling\n";
	cout << "c:     toggle direction coloring on/off\n";
	cout << "V/v:   increase decrease fluid viscosity\n";
	cout << "x:     toggle drawing matter on/off\n";
	cout << "y:     toggle drawing hedgehogs on/off\n";
	cout << "m:     toggle thru scalar coloring\n";
	cout << "a:     toggle the animation on/off\n";
	cout << "q:     quit\n\n";	
}

//------ INTERACTION CODE STARTS HERE -----------------------------------------------------------------

//display: Handle window redrawing events. Simply delegates to visualize().
void Application::display()
{
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
	visualization.visualize(simulation, winWidth, winHeight);    
	glFlush();
	glutSwapBuffers();
}

void Application::setWidgets()
{
    hue_spinner->set_float_val(visualization.getHue());
    val_spinner->set_float_val(visualization.getVal());
    sat_spinner->set_float_val(visualization.getSat());
    
    setHSVGray();
    
    colormap_menu->set_int_val(visualization.getColorMode());
    scale_radiobuttons->set_int_val(visualization.getScaleMode());
    glyph_scale_spinner->set_int_val(visualization.getVecScale());
    
    levels_spinner->set_int_val(visualization.getLevels());
}

void Application::setHSVGray()
{
    if (visualization.isHConst())
        hue_spinner->enable();
    else
        hue_spinner->disable();
    
    if (visualization.isSConst())
        sat_spinner->enable();
    else
        sat_spinner->disable();
    
    if (visualization.isVConst())
        val_spinner->enable();
    else
        val_spinner->disable();
    
    
}



//reshape: Handle window resizing (reshaping) events
void Application::reshape(int w, int h)
{
 	glViewport(0.0f, 0.0f, (GLfloat)w, (GLfloat)h);
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	gluOrtho2D(0.0, (GLdouble)w, 0.0, (GLdouble)h);
	winWidth = w; 
	winHeight = h;
}

//keyboard: Handle key presses
void Application::keyboard(unsigned char key, int x, int y)
{
	switch (key)
	{
		case 't': { simulation.change_timestep(-0.001); } break;
		case 'T': { simulation.change_timestep(0.001); } break;		
		case 'c': { visualization.toggle(Visualization::UseDirectionColoring); } break;
		case 'S': { visualization.scale_hedgehogs(1.2); } break;
		case 's': { visualization.scale_hedgehogs(0.8); } break;
		case 'V': { simulation.scale_viscosity(5.0); } break;
		case 'v': { simulation.scale_viscosity(0.2); } break;

		case 'x': 
		{
			visualization.toggle(Visualization::DrawSmoke);		
			if(visualization.is_enabled(Visualization::DrawSmoke))
				visualization.enable(Visualization::DrawVectorField);				  		  
		} break;

		case 'y': 
		{				
			visualization.toggle(Visualization::DrawVectorField);		
			if(visualization.is_enabled(Visualization::DrawVectorField))
				visualization.enable(Visualization::DrawSmoke);		
		} break;

		case 'm': { } break;

		case 'a': { simulation.toggle_frozen(); } break;

		case 'q': { quit(); } break;

		default: {} break;
	}

	// sync live variables
	GLUI_Master.sync_live_all();	
}

// drag: When the user drags with the mouse, add a force that corresponds to the direction of the mouse
//       cursor movement. Also inject some new matter into the field at the mouse location.
void Application::drag(int mx, int my)
{
	int xi, yi, X, Y; 
	double  dx, dy, len;
	static int lmx=0,lmy=0;				//remembers last mouse location

	// Compute the array index that corresponds to the cursor location
	xi = (int)clamp((double)(Simulation::DIM + 1) * ((double)mx / (double)winWidth));
	yi = (int)clamp((double)(Simulation::DIM + 1) * ((double)(winHeight - my) / (double)winHeight));

	X = xi; Y = yi;

	if (X > (Simulation::DIM - 1))  
		X = Simulation::DIM - 1; 
	
	if (Y > (Simulation::DIM - 1))  
		Y = Simulation::DIM - 1;
	
	if (X < 0) X = 0; if (Y < 0) Y = 0;

	// Add force at the cursor location
	my = winHeight - my;
	dx = mx - lmx; dy = my - lmy;
	len = sqrt(dx * dx + dy * dy);
	if (len != 0.0) {  dx *= 0.1 / len; dy *= 0.1 / len; }

	simulation.insert_force(X, Y, dx, dy);	
	lmx = mx; 
	lmy = my;
}

void Application::buttonHandler(int id)
{
	switch(id)
	{
            case QuitButton : { quit(); } break;
            case PauseButton: { pause(); } break;
            default: {} break;
	}
}

void Application::initUI()
{
    const int width = 400;
	glui = GLUI_Master.create_glui_subwindow(main_window, GLUI_SUBWINDOW_RIGHT);
	glui->set_main_gfx_window(main_window);

        
        GLUI_Panel *simulation_panel = new GLUI_Panel(glui, "Simulation");
        simulation_panel->set_w(width);
        pause_button = new GLUI_Button(simulation_panel, "Pause", PauseButton, buttonHandler);
        
        // options
        GLUI_Panel *options_panel = new GLUI_Panel(glui, "Visualization");
        options_panel->set_w(width);
        GLUI_Panel *vector_field_panel = new GLUI_Panel(options_panel,"Vector Field");
        new GLUI_Checkbox( vector_field_panel, "Draw Vector Field", &visualization.options[Visualization::DrawVectorField]);
        vector_dataset_radiobuttons = new GLUI_RadioGroup( vector_field_panel,
            NULL, -1, selectVectorDataSet);
        for (int iset = 0; iset < Visualization::VectorDataSetCount; iset++) {
            const char *datasetname = visualization.vectordatasets[iset].c_str();
            new GLUI_RadioButton( vector_dataset_radiobuttons, datasetname);
        }
        
        GLUI_Panel *colormap_panel_vec = new GLUI_Panel(vector_field_panel, "Colormap");
        colormap_panel_vec->set_w(width);
//        color_radiobuttons = new GLUI_RadioGroup( colormap_panel,
//                NULL, -1, selectColorMode);
//        for (int imode = 0; imode < Visualization::ColorModeCount; imode++) 
//        {
//            const char *colormodename = visualization.colormodes[imode].c_str();
//            new GLUI_RadioButton( color_radiobuttons, colormodename);
//        }
        colormap_menu_vec = new GLUI_Listbox(colormap_panel_vec,"Select colormap", NULL, -1, selectColorModeVec);
        for (int imode = 0; imode < Visualization::ColorModeCount; imode++) {
            const char *colormodename = visualization.colormodes[imode].c_str();
            colormap_menu_vec->add_item(imode, colormodename);
        }
        levels_spinner_vec = new GLUI_Spinner(colormap_panel_vec, "# of colors", 
                GLUI_SPINNER_INT, NULL, -1, selectNLevelsVec);
        
        levels_spinner_vec->set_int_limits(2,256,GLUI_LIMIT_CLAMP);
        
        hue_spinner_vec = new GLUI_Spinner(colormap_panel_vec, "Hue",
                GLUI_SPINNER_FLOAT, NULL, -1, setHueVec);
        
        hue_spinner_vec->set_float_limits(0.0, 1.0, GLUI_LIMIT_CLAMP);        
        
        sat_spinner_vec = new GLUI_Spinner(colormap_panel_vec, "Saturation",
                GLUI_SPINNER_FLOAT, NULL, -1, setSatVec);
        
        sat_spinner_vec->set_float_limits(0.0, 1.0, GLUI_LIMIT_CLAMP);        
        
        val_spinner_vec = new GLUI_Spinner(colormap_panel_vec, "Value",
                GLUI_SPINNER_FLOAT, NULL, -1, setValVec);
        
        val_spinner_vec->set_float_limits(0.0, 1.0, GLUI_LIMIT_CLAMP);
             
        // scaling mode
        GLUI_Panel *scale_panel_vec = new GLUI_Panel(colormap_panel_vec, "Color map scaling");
        scale_panel_vec->set_w(width);        
        scale_radiobuttons_vec = new GLUI_RadioGroup( scale_panel_vec,
                NULL, -1, selectScalingMode);
        new GLUI_RadioButton( scale_radiobuttons_vec, "Automatic");
        new GLUI_RadioButton( scale_radiobuttons_vec, "Manual");
        
        glyph_scale_spinner = new GLUI_Spinner(vector_field_panel,"Glyph Scaling",
                GLUI_SPINNER_FLOAT,NULL, -1,selectVectorScale);
        glyph_scale_spinner->set_float_limits(10,5000,GLUI_LIMIT_CLAMP);
        new GLUI_Checkbox(vector_field_panel,"Use log scaling",&visualization.options[Visualization::UseLogScaling]);
        
        
        
        GLUI_Panel *scalar_values_panel = new GLUI_Panel(options_panel, "Scalar Values");
        new GLUI_Checkbox( scalar_values_panel, "Draw Smoke", &visualization.options[Visualization::DrawSmoke]);
        scalar_dataset_radiobuttons = new GLUI_RadioGroup( scalar_values_panel,
                NULL, -1, selectScalarDataSet);
        for (int iset = 0; iset < Visualization::DataSetCount; iset++)
        {
            const char *datasetname = visualization.datasets[iset].c_str();
            new GLUI_RadioButton( scalar_dataset_radiobuttons, datasetname);
        }
        
        
        // colorbar/legend
        GLUI_Panel *colormap_panel = new GLUI_Panel(scalar_values_panel, "Colormap");
        colormap_panel->set_w(width);
//        color_radiobuttons = new GLUI_RadioGroup( colormap_panel,
//                NULL, -1, selectColorMode);
//        for (int imode = 0; imode < Visualization::ColorModeCount; imode++) 
//        {
//            const char *colormodename = visualization.colormodes[imode].c_str();
//            new GLUI_RadioButton( color_radiobuttons, colormodename);
//        }
        colormap_menu = new GLUI_Listbox(colormap_panel,"Select colormap", NULL, -1, selectColorMode);
        for (int imode = 0; imode < Visualization::ColorModeCount; imode++) {
            const char *colormodename = visualization.colormodes[imode].c_str();
            colormap_menu->add_item(imode, colormodename);
        }
        levels_spinner = new GLUI_Spinner(colormap_panel, "# of colors", 
                GLUI_SPINNER_INT, NULL, -1, selectNLevels);
        
        levels_spinner->set_int_limits(2,265,GLUI_LIMIT_CLAMP);
        
        hue_spinner = new GLUI_Spinner(colormap_panel, "Hue",
                GLUI_SPINNER_FLOAT, NULL, -1, setHue);
        
        hue_spinner->set_float_limits(0.0, 1.0, GLUI_LIMIT_CLAMP);        
        
        sat_spinner = new GLUI_Spinner(colormap_panel, "Saturation",
                GLUI_SPINNER_FLOAT, NULL, -1, setSat);
        
        sat_spinner->set_float_limits(0.0, 1.0, GLUI_LIMIT_CLAMP);        
        
        val_spinner = new GLUI_Spinner(colormap_panel, "Value",
                GLUI_SPINNER_FLOAT, NULL, -1, setVal);
        
        val_spinner->set_float_limits(0.0, 1.0, GLUI_LIMIT_CLAMP);
             
        // scaling mode
        GLUI_Panel *scale_panel = new GLUI_Panel(colormap_panel, "Color map scaling");
        scale_panel->set_w(width);
        scale_radiobuttons = new GLUI_RadioGroup( scale_panel,
                NULL, -1, selectScalingMode);
        new GLUI_RadioButton( scale_radiobuttons, "Automatic");
        new GLUI_RadioButton( scale_radiobuttons, "Manual");
        
        
        // App panel
        GLUI_Panel *app_panel = new GLUI_Panel(glui, "Application");
        app_panel->set_w(width);
        new GLUI_Button(app_panel, "Quit", QuitButton, buttonHandler);

}

void Application::quit()
{
	exit(0);
}

void Application::pause()
{
    simulation.toggle_frozen();
    if (simulation.frozen) {
        pause_button->set_name("Unpause");
    }
    else
    {
        pause_button->set_name("Pause");
    }
}

void Application::selectVectorScale(int dum)
{
    float scale = glyph_scale_spinner->get_float_val();
    visualization.set_hedgehog_scale(scale);
}

void Application::selectColorMode(int dum)
{
    int colorMode = colormap_menu->get_int_val();
    visualization.setColorMode(colorMode);
    setHSVGray();
}

void Application::selectNLevels(int dum)
{
    int levels = levels_spinner->get_int_val();
    visualization.setLevels(levels);    
}

void Application::selectScalarDataSet(int dum) 
{
    int dataSet = scalar_dataset_radiobuttons->get_int_val();
    visualization.setDataSet(dataSet);
}

void Application::selectVectorDataSet(int dum) 
{
    int dataSet = vector_dataset_radiobuttons->get_int_val();
    visualization.setVectorDataSet(dataSet);
}

void Application::selectScalingMode(int dum)
{
    int scaleMode = scale_radiobuttons->get_int_val();
    visualization.setScaleMode(scaleMode);
}

void Application::setHue(int dum)
{
    double hue = hue_spinner->get_float_val();
    visualization.setHue(hue);
}

void Application::setSat(int dum)
{
    double sat = sat_spinner->get_float_val();
    visualization.setSat(sat);
}

void Application::setVal(int dum)
{
    double val = val_spinner->get_float_val();
    visualization.setVal(val);
}

void Application::selectColorModeVec(int dum)
{
    int colorMode = colormap_menu_vec->get_int_val();
    visualization.setColorModeVec(colorMode);
    setHSVGray();
}

void Application::selectNLevelsVec(int dum)
{
    int levels = levels_spinner_vec->get_int_val();
    visualization.setLevelsVec(levels);    
}

void Application::selectScalingModeVec(int dum)
{
    int scaleMode = scale_radiobuttons_vec->get_int_val();
    visualization.setScaleModeVec(scaleMode);
}

void Application::setHueVec(int dum)
{
    double hue = hue_spinner_vec->get_float_val();
    visualization.setHueVec(hue);
}

void Application::setSatVec(int dum)
{
    double sat = sat_spinner_vec->get_float_val();
    visualization.setSatVec(sat);
}

void Application::setValVec(int dum)
{
    double val = val_spinner_vec->get_float_val();
    visualization.setValVec(val);
}

